﻿using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace UtilitiesLib
{
    public static class DataSerializer
    {
        #region XML serialization/deserialization

        /// <summary>
        /// Serializes object as XML document.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <returns>XmlDocument containing serialized object</returns>
        public static XmlDocument SerializeXml<T>(T data)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            XmlDocument doc = new XmlDocument();

            using (TextWriter writer = new StringWriter())
            {
                serializer.Serialize(writer, data);
                writer.Flush();
                doc.LoadXml(writer.ToString());
            }

            return doc;
        }

        /// <summary>
        /// Deserializes object from XML document.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="doc">XML document</param>
        /// <returns>Deserialized object</returns>
        public static T DeserializeXml<T>(XmlDocument doc)
        {
            T data;

            XmlSerializer deserializer = new XmlSerializer(typeof(T));
            using (StringReader reader = new StringReader(doc.InnerXml))
            {
                object obj = deserializer.Deserialize(reader);
                data = (T)obj;
            }
            return data;
        }

        /// <summary>
        /// Serializes object as XML and writes it to the file.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <param name="path">Path to the file</param>
        public static void SerializeXmlToFile<T>(T data, string path)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            using (FileStream file = File.OpenWrite(path))
            {
                serializer.Serialize(file, data);
            }
        }

        /// <summary>
        /// Deserializes object from XML file.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="path">Path to the file</param>
        /// <returns>Deserialized object</returns>
        public static T DeserializeXmlFromFile<T>(string path)
        {
            T data;

            XmlSerializer deserializer = new XmlSerializer(typeof(T));
            using (FileStream reader = File.OpenRead(path))
            {
                object obj = deserializer.Deserialize(reader);
                data = (T)obj;
            }
            return data;
        }

        #endregion XML serialization/deserialization

        #region Binary serialization/deserialization

        /// <summary>
        /// Serializes object as binary data.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <returns>Byte array containing serialized object</returns>
        public static byte[] SerializeBinary<T>(T data)
        {
            byte[] result;

            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream())
            {
                formatter.Serialize(stream, data);
                stream.Flush();
                result = stream.ToArray();
            }
            return result;
        }

        /// <summary>
        /// Deserializes object from byte array.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="data">Binary data to deserialize</param>
        /// <returns>Deserialized object</returns>
        public static T DeserializeBinary<T>(byte[] data)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            T result;

            using (MemoryStream stream = new MemoryStream(data))
            {
                result = (T)formatter.Deserialize(stream);
            }
            return result;
        }

        /// <summary>
        /// Serializes object as binary data and writes it to the file.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <param name="path">Path to the file</param>
        public static void SerializeBinaryToFile<T>(T data, string path)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream fs = File.OpenWrite(path))
            {
                formatter.Serialize(fs, data);
            }
        }

        /// <summary>
        /// Deserializes object from binary file.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="path">Path to the file</param>
        /// <returns>Deserialized object</returns>
        public static T DeserializeBinaryFromFile<T>(string path)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            T result;

            using (FileStream fs = File.OpenRead(path))
            {
                result = (T)formatter.Deserialize(fs);
            }
            return result;
        }

        #endregion Binary serialization/deserialization

        #region Async methods

        /// <summary>
        /// Serializes object as XML document. Async function.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <returns>XmlDocument containing serialized object</returns>
        public static Task<XmlDocument> SerializeXmlAsync<T>(T data)
        {
            return Task<XmlDocument>.Run(() => SerializeXml<T>(data));
        }

        /// <summary>
        /// Deserializes object from XML document. Async function.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="doc">XML document</param>
        /// <returns>Deserialized object</returns>
        public static Task<T> DeserializeXmlAsync<T>(XmlDocument doc)
        {
            return Task<T>.Run(() => DeserializeXml<T>(doc));
        }

        /// <summary>
        /// Serializes object as XML and writes it to the file. Async method.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <param name="path">Path to the file</param>
        public static Task SerializeXmlToFileAsync<T>(T data, string path)
        {
            return Task.Run(() => SerializeXmlToFile<T>(data, path));
        }

        /// <summary>
        /// Deserializes object from XML file. Async method.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="path">Path to the file</param>
        /// <returns>Deserialized object</returns>
        public static Task<T> DeserializeXmlFromFileAsync<T>(string path)
        {
            return Task<T>.Run<T>(() => DeserializeXmlFromFile<T>(path));
        }

        /// <summary>
        /// Serializes object as binary data. Async function.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <returns>Byte array containing serialized object</returns>
        public static Task<byte[]> SerializeBinaryAsync<T>(T data)
        {
            return Task<byte[]>.Run(() => SerializeBinary<T>(data));
        }

        /// <summary>
        /// Deserializes object from byte array. Async function.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="data">Binary data to deserialize</param>
        /// <returns>Deserialized object</returns>
        public static Task<T> DeserializeBinaryAsync<T>(byte[] data)
        {
            return Task<T>.Run(() => DeserializeBinary<T>(data));
        }

        /// <summary>
        /// Serializes object as binary data and writes it to the file. Async method.
        /// </summary>
        /// <typeparam name="T">Type of the object to be serialized</typeparam>
        /// <param name="data">Object to serialize</param>
        /// <param name="path">Path to the file</param>
        public static Task SerializeBinaryToFileAsync<T>(T data, string path)
        {
            return Task.Run(() => SerializeBinaryToFile<T>(data, path));
        }

        /// <summary>
        /// Deserializes object from binary file. Async method.
        /// </summary>
        /// <typeparam name="T">Type of the serialized object</typeparam>
        /// <param name="path">Path to the file</param>
        /// <returns>Deserialized object</returns>
        public static Task<T> DeserializeBinaryFromFileAsync<T>(string path)
        {
            return Task<T>.Run<T>(() => DeserializeBinaryFromFile<T>(path));
        }

        #endregion Async methods
    }
}